
package picsim;

import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;

/**
 *
 * @author Daniel
 * @brief Ist für das Öffnen und die Abarbeitung der Source-File (LST) zuständig. Übergibt die Assembler-Befehle
 * an die Prozessor-Klasse "PIC".
 *
 */
public class FileMan {

    private String filePath;
    final private String tmpFileName = "PICSim.tmp";
    private int lineCounter = 0;
    
    final private String HEX_REGEX = "[0-9A-Fa-f]{4}";

    /**
     *
     * @brief Konstruktor der Klasse FileMan. Setzt die Variable filePath auf null
     */
    public FileMan() {
      filePath = null;
    }

    /**
     * 
     * @param path (String)
     * @brief speichert den Pfad (absolut) ab
     */
    private void setFilePath(String path) {
        filePath = path;
    }

    /**
     * 
     * @return filePath (String)
     * @brief gibt den aktuellen Dateipfad zurück
     */
    public String getFilePath() {
        return filePath;
    }

    /**
     * @brief Setzt den Dateipfad auf null
     */
    public void clearFilePath() {
        setFilePath(null);
    }

    /**
     * @brief bietet dem Benutzer einen Dateiauswahl-Dialog und speichert beim Öffnen einer Datei den absoluten Pfad zur Datei ab
     */
    public boolean openFile() {

        JFileChooser chooser = new JFileChooser();
        
        // set file filter for File Open Dialog
        FileNameExtensionFilter filter = new FileNameExtensionFilter("Quelltext Dateien (PICSim)", "lst", "txt", "log");
        chooser.addChoosableFileFilter(filter);
        // *********
        
        int returnVal = chooser.showOpenDialog(null);
        
        // file selected and dialog ended with "OK"-Button
        if(returnVal == JFileChooser.APPROVE_OPTION) {
            setFilePath(chooser.getSelectedFile().getAbsolutePath()); // save filePath
            setLineCounter(); // save counter of lines
            return true;
        }
        else if(returnVal == JFileChooser.CANCEL_OPTION) {
            //System.out.println("FileChooser Cancel geklickt");
            return false;
        }
        else {
            return false;
        }
        
    }

    /**
     * @brief schließt die Datei
     */
    public void closeFile() {

        clearFilePath();
        lineCounter = 0;

        // delete tmp-file
        File file = new File(tmpFileName);
	if(file.exists()){
            file.delete();
	}
        // *********
       
    }

    /**
     * @brief Zählt die Zeilen-Anzahl der geöffneten Datei und speichert diese Zahl ab
     */
    private void setLineCounter() {

       int counter=1;
        try {
		BufferedReader in = new BufferedReader(new FileReader(filePath));
		while (in.readLine() != null) {
                    counter++;
		}
	} catch (IOException e) {
		//e.printStackTrace();
            System.out.println("[FileMan.setLineCounter():] Da lief wohl was schief...!");
	}

       lineCounter = counter;

    }

    /**
     * 
     * @return lineCounter (int)
     * @brief Gibt die Zeilen-Anzahl (insgesamt) der geöffneten LST-Datei zurück
     */
    public int getLineCounter() {
        return lineCounter;
    }

    /**
     *
     * @param path (String)
     * @return content (String)
     * @brief Liest die angegebene Datei zeilenweise ein und gibt diese am Ende komplett zurück
     */
    public String getFileContent(String path) {
        
        String content = "";
        try {
		BufferedReader in = new BufferedReader(new FileReader(path));
		String row = null;
		while ((row = in.readLine()) != null) {
                    content += row+System.getProperty("line.separator");
		}

	} catch (IOException e) {
		//e.printStackTrace();
            System.out.println("[FileMan.getFileContent():] Fehler!");
	}

        return content;

    }

    /**
     *
     * @param path (String)
     * @param line (int)
     * @param breakLine (boolean)
     * @return content (String)
     * @brief Liest die Zeile der angegebenen Datei ein und gibt den Zeileninhalt zurück
     * Wenn <code>breakLine</code> gesetzt ist, wird nach der zeile ein absatz-zeichen eingefügt
     */
    public String getFileContent(String path, int line, boolean breakLine) {

        String content = "";
        try {
		BufferedReader in = new BufferedReader(new FileReader(path));
		String row = null;
                int lineCount = 1;
		while ((row = in.readLine()) != null) {
                    if(lineCount == line) {                        
                        content = row;
                        if(breakLine) content += System.getProperty("line.separator");
                        break;
                    }
                    lineCount++;
		}
	} catch (IOException e) {
		//e.printStackTrace();
            System.out.println("[FileMan.getFileContent():] Fehler!");
	}

        return content;

    }

    /**
     *
     * @return codePreview (String)
     * @brief Schreibt die temporäre Datei undg ibt den gesamten Inhalt der Datei als String zurück
     * 
     */
    public String getCodeView() {

        String codePreview = "";

        // write content of tmpFile
        writeCodePreviewToFile();

        // get file Content of tmpFile to show in GUI
        codePreview = getFileContent(tmpFileName);
        
        return codePreview;

    }

    /**
     * @brief Erzeugt eine Datei, in der Kommentare, Opcode und Whitespace entfernt sind (Übersichtlichkeit)
     */
    private void writeCodePreviewToFile() {

        String line = "", codePreview = "";
        int semicolonPos = 0;
        int lines = getLineCounter();
        File tmp = new File(tmpFileName);

        if(tmp.exists()) tmp.delete(); // delete if exists
        
        try {
            FileOutputStream writeStream = new FileOutputStream(tmpFileName);            

            for(int i=1; i<lines; i++) { // go from line 1 to endPos of file

                line = getFileContent(filePath, i, false); // get Content of line x

                // delete opcodes and linenumbers from LST-file
                codePreview = line.substring(27, line.length());

                // remove comments (starts with ;) from LST-File
                semicolonPos = codePreview.indexOf(";");
                if(semicolonPos >= 0) codePreview = codePreview.substring(0, semicolonPos);
                // *********                

                // remove whitespace
                //codePreview = codePreview.trim();

                
                // write no output if line will empty on trim()
                if(!codePreview.trim().equals("")) {

                    // add line break
                    codePreview += System.getProperty("line.separator");

                    // write line content per byte to tmpFile
                    for (int l=0; l < codePreview.length(); l++){
                        writeStream.write((byte)codePreview.charAt(l));
                    }
                    // *********

                }
                else {
                    String comment = ";"+System.getProperty("line.separator");
                    for(int j=0; j<comment.length(); j++) {
                        writeStream.write((byte) comment.charAt(j));
                    }

                }


                // empty codePreview for next line
                codePreview = "";
                
            }

            writeStream.close();
            
        }
        catch(IOException e) {
            System.out.println("Fehler beim Speichern der Datei!");
        }


    }

    /**
     *
     * @param row (int)
     * @param lineBreak (boolean)
     * @return memAddr (String)
     * @brief Gibt den 4-stelligen Opcode der Zeile als String zurück
     */
    public String getOpcodeFromLST(int row, boolean lineBreak) {

        String opcode = "";
        String line = getFileContent(filePath, row, lineBreak);

        // remove whitespace in front and after the content of line
        opcode = line.trim();
        // *********

        // get opcode (4 bit)
        try {
            opcode = opcode.substring(5, 9);
        }
        catch(IndexOutOfBoundsException e) {
            return "null";
        }
        // *********

        if(!testOpcode(opcode)) opcode = "null";

        return opcode;

    }


    /**
     *
     * @param row (int)
     * @return memAddr (String)
     * @brief Gibt die 4-stelligen Speicheradresse der Zeile als String zurück
     */
    public String getMemoryAddressOfLine(int row) {

        String memAddr = getFileContent(filePath, row, false);

        // get memAddr (4 bit)
        try {
            memAddr = memAddr.substring(0, 4);
        }
        catch(IndexOutOfBoundsException e) {
            return "null";
        }
        // *********

        if(!memAddr.matches(HEX_REGEX)) return "null"; // no Memory Address found

        return memAddr;

    }

    /**
     * 
     * @param memAddr (String)
     * @return line (int)
     * @brief Sucht in der Quelltextdatei die Zeile der angegebenen Speicheradresse
     */
    public int getLineOfMemoryAddress(String memAddr) {

        int line = 1;

        String lineContent = "";

        for(line=1; line<getLineCounter();line++) {
            lineContent = getFileContent(filePath, line, false);
            lineContent = lineContent.substring(0,4); // only memory address string

            if(lineContent.contains(memAddr)) break; // string found, return with line in which found the given Memory Address
        }

        return line;
    }


    /**
     *
     * @param opcode (String)
     * @return true|false (boolean)
     * @brief Testet den übergebenen <code>memAddr</code>, ob der reguläre Ausdruck für Hex-Zahlen zutrifft
     */
    public boolean testOpcode(String opcode) {

        if(opcode.matches(HEX_REGEX)) return true;
        else return false;

    }
    

    /**
     * 
     * @param line (int)
     * @return startPos (int)
     * @brief Berechnet die Anfangs-Position der übergebenen <code>line</code> und gibt diese Position zurück.
     */
    public int getStartOfLine(int line) {

        int startPos = 0;

        if(line == 1) {
            return 0;
        }
        if(line == 2) {
            String lineContent = getFileContent(tmpFileName, 1, true);
            return lineContent.length()-1;
        }
        if(line > 2) {

            if(line == 3) {
                int line1 = getFileContent(tmpFileName, 1, true).length()-1;
                int line2 = getFileContent(tmpFileName, 2, true).length()-1;

                return line1+line2;
            }
            else {
                
                for(int row=1; row<line; row++) {
                    
                    String lineContent = getFileContent(tmpFileName, row, true);
                    startPos += lineContent.length()-1;

                }

            }

        }

        return startPos;

    }


    /**
     *
     * @param line (int)
     * @return endPos (int)
     * @brief Berechnet die Position des letzten Zeichens der übergebenen <code>line</code> und gibt diese Position zurück.
     */
    public int getEndOfLine(int line) {

        int endPos = 0;

        String lineContent = getFileContent(tmpFileName, line, false);
        endPos = getFileContent(tmpFileName).lastIndexOf(lineContent);

        int start = getStartOfLine(line);
        
        endPos = start + lineContent.length();
        
        return endPos;

    }

    /**
     * 
     * @return line (int)
     * @brief Sucht in jeder Zeile nach dem String, der den Start des Programms definiert.
     * Wenn der String gefunden wird, gibt die Methode die Zahl der gefundenen Zeile zurück
     * @deprecated 
     */
    public int findStartOfProgram() {
        
        return getLineOfMemoryAddress("0000"); // find first operation to execute
        
    }

    /**
     *      
     * @return lastOpcode (String)
     * @brief Sucht den letzten Opcode aus der LST-File
     */
    public String getLastOpcode() {

        int tmp_count = getLineCounter();

        while(getMemoryAddressOfLine(tmp_count).equals("null")) {
            tmp_count--;
        }

        return getMemoryAddressOfLine(tmp_count);
        
    }

    
}
